package wangjg.security.resource.factory.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import wangjg.commons.bean.XmlBeanParser;
import wangjg.security.core.AuthoritySet;
import wangjg.security.core.ResourceFactory;
import wangjg.security.core.validator.ExpressionValidator;
import wangjg.security.deprecated.HttpResource;
import wangjg.security.deprecated.Resource;
import wangjg.security.deprecated.ResourceValidator;
import wangjg.security.deprecated.RoleValidator;


/**
 * 从xml文件加载配置(工具类)实现ResourceFactory
 * @author wangjg
 *
 */
public class XmlResourceFactory implements ResourceFactory{
	private Set resources;
	private String path;
	
	private RoleFactory roleFactory = new RoleFactory(){
		public Object getRole(String str) {
			return str;
		}
	};

	public Set<Resource> load() {
		if(this.resources!=null){
			return this.resources;
		}
		return this._load();
	}
	private synchronized Set<Resource> _load() {
		if(this.resources!=null){
			return this.resources;
		}
		this.resources=new LinkedHashSet<Resource>();
		InputStream is = null;
		try {
			is = this.getClass().getResourceAsStream(path);
			SAXReader xreader = new SAXReader();
			Document document = xreader.read(is);
			this.loadIncludeDocs(document);
			this.loadSimpleHttpResources(document);
			this.loadHttpResources(document);
		} catch (DocumentException e) {
			throw new RuntimeException(e);
		}finally{
			try {
				if(is!=null)is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return this.resources;
	}
	
	private void loadIncludeDocs(Document document){
		List list=document.getRootElement().elements("include");
		for(Object node: list){
			Element element = (Element)node;
			String path=element.attributeValue("file");
			InputStream is = null;
			try{
				is = getClass().getResourceAsStream(path);
				SAXReader xreader = new SAXReader();
				Document doc = xreader.read(is);
				XmlBeanParser.copyChildren(doc, document);
			}catch(Exception ex){
				throw new RuntimeException(ex);
			}finally{
				try {
					if(is!=null)is.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		}
	}
	/*
	 * handle <url/>标签
	 */
	private void loadSimpleHttpResources(Document document) {
		Element root = document.getRootElement();
		List list = root.selectNodes("/resources/url");
        for(Object node: list){
            Element e = (Element) node;
            HttpResource httpResource=this.createSimpleHttpResource(e);
            this.resources.add(httpResource);
        }
	}
	
	private HttpResource createSimpleHttpResource(Element e){
        String pattern = e.attributeValue("pattern");
        HttpResource httpResource=new HttpResource();
        httpResource.setPattern(pattern);
        RoleValidator validator = new RoleValidator();
        String roleNames = e.getTextTrim();
        if("*".equals(roleNames)){
        	validator.setAllowAll(true);
        }else{
        	validator.setRoles(this.getRoles(roleNames));
        }
        ArrayList<ResourceValidator> validators = new ArrayList<ResourceValidator>();
        validators.add(validator);
    	httpResource.setValidators(validators);
        return httpResource;
	}
	private Set getRoles(String roleNames){
        String[] rna = roleNames.split(",");
        Set roles = new HashSet();
        for(int i=0;i<rna.length;i++){
        	Object role = this.roleFactory.getRole(rna[i]);
            roles.add(role);
        }
        return roles;
	}
	/*
	 * handle 资源标签<http/>
	 */
	private void loadHttpResources(Document document){
		Element root = document.getRootElement();
		List list = root.selectNodes("/resources/http-resources/http");
        for(Object node: list){
            Element e = (Element) node;
            HttpResource httpResource=this.createHttpResource(e);
            this.resources.add(httpResource);
        }
	}
	/*
	 * 处理单个的<Resource>标签，被打方法调用
	 */
	private HttpResource createHttpResource(Element element){
		HttpResource httpResource = new HttpResource();
		XmlBeanParser2.setParameters(httpResource, element);
        List<Element> ves = element.elements("validator");
        ArrayList<ResourceValidator> validators = new ArrayList<ResourceValidator>();
        for(Element ve: ves){
        	String type = ve.attributeValue("type");
    		if("roleValidator".equals(type)){
    			Element pe = (Element) ve.selectSingleNode("param");
    			String roleNames = pe.getTextTrim();
    			RoleValidator v = new RoleValidator();
    			v.setRoles(this.getRoles(roleNames));
        		validators.add(v);
    		}else if("expressionValidator".equals(type)){
    			ExpressionValidator v = new ExpressionValidator();
    			XmlBeanParser2.setParameters(v, ve);
        		validators.add(v);
    		}
        }
        httpResource.setValidators(validators);
        return httpResource;
	}
	/*
	 * 资源分组也就是一个可以被分配的权限单位<authority/>
	 */
	private void loadAuthorities(Document document){
		Element root = document.getRootElement();
		Element ce=(Element) root.selectSingleNode("/resource/authorities/class");
		try {
			if(ce==null) return;
			String className = ce.getTextTrim();
			Class authorityClass = Class.forName(className);
			
			if(authorityClass==null) return ;
			
			List list = root.selectNodes("/resource/authorities/authority");
	        for(Object node: list){
	            Element e = (Element) node;
	            Object a = authorityClass.newInstance();
	            XmlBeanParser2.setParameters(a, e);
				this._handleAuthorityElement(a,e);
	        }
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	//被loadHttpGroups方法调用（拆分代码） 
	private void _handleAuthorityElement(Object a,Element e) {
		List<Node> ref = e.selectNodes("./resources/ref");
		for(Node node: ref){//引用的资源
			if(node.getNodeType()!=Node.ELEMENT_NODE){
				continue;
			}
			Element re=(Element)node;
			String property = re.attributeValue("byProperty");
			String value = re.getTextTrim();
			List<Resource> list=this.queryResources(property, value.split("\\,"));
			for(Resource res:list){
				AuthoritySet ah=(AuthoritySet)res;
				ah.getAuthorities().add(a);
			}
		}
		//处理<authority>标签中内嵌的资源
		List<Node> http = e.selectNodes("resources/http");
		for(Node node:http){//新配置的资源
			if(node.getNodeType()!=Node.ELEMENT_NODE){
				continue;
			}
			HttpResource r=this.createHttpResource((Element)node);
			r.getAuthorities().add(a);
		}
		
		//处理<url>标签中内嵌的资源
		List<Node> urls = e.selectNodes("resources/url");
		for(Node node:urls){//新配置的资源
			if(node.getNodeType()!=Node.ELEMENT_NODE){
				continue;
			}
			HttpResource r=this.createSimpleHttpResource((Element)node);
			r.getAuthorities().add(a);
		}
	}
	/*
	 * 根据属性查询资源
	 */
	private List<Resource> queryResources(String property, String...values){
		ArrayList list = new ArrayList();
		for(Object r:this.resources){
			for(String v:values){
				try {
					if(BeanUtils.getProperty(r, property).equals(v)){
						list.add(r);
					}
				} catch (Exception e) {
				}
			}
		}
		return list;
	}
	
	public void setPath(String path) {
		this.path = path;
	}

	public String getPath() {
		return path;
	}

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		XmlResourceFactory f=new XmlResourceFactory();
		f.setPath("/security-resources4.xml");
		f.load();
		System.out.println(f.resources);
	}
	
	@Override
	public Iterator<Resource> iterator() {
		return this.load().iterator();
	}
	
	public static class XmlBeanParser2 extends XmlBeanParser{
		public static void setParameters(Object bean, Element beanElement) {
			set(bean, beanElement, "param", null);
		}
	}

}
